/*
 * Copyright (c) 1989 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */


//#include "tzfile.h"
//#include <fcntl.h>
#include <sys/stat.h>
//#include "timelocal.h"
#include <time.h>




static const char *mon[12] =
{
    "Jan", "Feb", "Mar", "Apr",
    "May", "Jun", "Jul", "Aug",
    "Sep", "Oct", "Nov", "Dec"
};

static const char *month[12] =
{
    "January", "February", "March", "April",
    "May", "June", "July", "August",
    "September", "October", "November", "December"
};

static const char *wday[7] =
{
    "Mon", "Tue", "Wed", "Thi",
    "Fri", "Sat", "Sun"
};

static const char *weekday[7] =
{
    "Monday", "Tuesday", "Wednesday", "Thirsday",
    "Friday", "Saturday", "Sunday"
};

static char *c_fmt = "%d/%m/%y %H:%M";







static char *	_add (const char *, char *, const char *);
static char *	_conv (int, const char *, char *, const char *);
static char *	_fmt (const char *, const struct tm *, char *, const char *);

size_t strftime (char *, size_t, const char *, const struct tm *);

extern char *	tzname[];

size_t strftime(
             char *const s,
             const size_t maxsize,
             const char *const format,
             const struct tm *const t )
{
    char *p;

    //tzset();
    p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize);
    if (p == s + maxsize)
        return 0;
    *p = '\0';
    return p - s;
}

static char *
_fmt(format, t, pt, ptlim)
const char *format;
const struct tm *const t;
char *pt;
const char *const ptlim;
{
    for ( ; *format; ++format) {
        if (*format == '%') {
        label:
            switch (*++format) {
            case '\0':
                --format;
                break;
            case 'A':
                pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
                          //"?" : Locale->weekday[t->tm_wday],
                          "?" : weekday[t->tm_wday],
                          pt, ptlim);
                continue;
            case 'a':
                pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ?
                          //"?" : Locale->wday[t->tm_wday],
                          "?" : wday[t->tm_wday],
                          pt, ptlim);
                continue;
            case 'B':
                pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
                          //"?" : Locale->month[t->tm_mon],
                          "?" : month[t->tm_mon],
                          pt, ptlim);
                continue;
            case 'b':
            case 'h':
                pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ?
                          //"?" : Locale->mon[t->tm_mon],
                          "?" : mon[t->tm_mon],
                          pt, ptlim);
                continue;
            case 'C':
                /*
                 ** %C used to do a...
                 **	_fmt("%a %b %e %X %Y", t);
                 ** ...whereas now POSIX 1003.2 calls for
                 ** something completely different.
                 ** (ado, 5/24/93)
                 */
                pt = _conv((t->tm_year + TM_YEAR_BASE) / 100,
                           "%02d", pt, ptlim);
                continue;
            case 'c':
                //pt = _fmt(Locale->c_fmt, t, pt, ptlim);
                pt = _fmt(c_fmt, t, pt, ptlim);
                continue;
            case 'D':
                pt = _fmt("%m/%d/%y", t, pt, ptlim);
                continue;
            case 'd':
                pt = _conv(t->tm_mday, "%02d", pt, ptlim);
                continue;
            case 'E':
            case 'O':
                /*
                 ** POSIX locale extensions, a la
                 ** Arnold Robbins' strftime version 3.0.
                 ** The sequences
                 **	%Ec %EC %Ex %Ey %EY
                 **	%Od %oe %OH %OI %Om %OM
                 **	%OS %Ou %OU %OV %Ow %OW %Oy
                 ** are supposed to provide alternate
                 ** representations.
                 ** (ado, 5/24/93)
                 */
                goto label;
            case 'e':
                pt = _conv(t->tm_mday, "%2d", pt, ptlim);
                continue;
            case 'H':
                pt = _conv(t->tm_hour, "%02d", pt, ptlim);
                continue;
            case 'I':
                pt = _conv((t->tm_hour % 12) ?
                           (t->tm_hour % 12) : 12,
                           "%02d", pt, ptlim);
                continue;
            case 'j':
                pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
                continue;
            case 'k':
                /*
                 ** This used to be...
                 **	_conv(t->tm_hour % 12 ?
                 **		t->tm_hour % 12 : 12, 2, ' ');
                 ** ...and has been changed to the below to
                 ** match SunOS 4.1.1 and Arnold Robbins'
                 ** strftime version 3.0.  That is, "%k" and
                 ** "%l" have been swapped.
                 ** (ado, 5/24/93)
                 */
                pt = _conv(t->tm_hour, "%2d", pt, ptlim);
                continue;
#ifdef KITCHEN_SINK
            case 'K':
                /*
                 ** After all this time, still unclaimed!
                 */
                pt = _add("kitchen sink", pt, ptlim);
                continue;
#endif /* defined KITCHEN_SINK */
            case 'l':
                /*
                 ** This used to be...
                 **	_conv(t->tm_hour, 2, ' ');
                 ** ...and has been changed to the below to
                 ** match SunOS 4.1.1 and Arnold Robbin's
                 ** strftime version 3.0.  That is, "%k" and
                 ** "%l" have been swapped.
                 ** (ado, 5/24/93)
                 */
                pt = _conv((t->tm_hour % 12) ?
                           (t->tm_hour % 12) : 12,
                           "%2d", pt, ptlim);
                continue;
            case 'M':
                pt = _conv(t->tm_min, "%02d", pt, ptlim);
                continue;
            case 'm':
                pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
                continue;
            case 'n':
                pt = _add("\n", pt, ptlim);
                continue;
            case 'p':
                // todo locale
                pt = _add((t->tm_hour >= 12) ?
                          "pm" :
                          "am",
                          pt, ptlim);
                continue;
            case 'R':
                pt = _fmt("%H:%M", t, pt, ptlim);
                continue;
            case 'r':
                pt = _fmt("%I:%M:%S %p", t, pt, ptlim);
                continue;
            case 'S':
                pt = _conv(t->tm_sec, "%02d", pt, ptlim);
                continue;
            case 's':
                {
                    struct tm	tm;
                    //char		buf[INT_STRLEN_MAXIMUM(time_t) + 1];
                    char		buf[32];
                    time_t		mkt;

                    tm = *t;
                    mkt = mktime(&tm);
                    if(1) // (TYPE_SIGNED(time_t))
                    {
                        (void) snprintf(buf, sizeof(buf), "%ld", (long) mkt);
                    }
                    else
                    {
                        (void) snprintf(buf, sizeof(buf), "%lu", (unsigned long) mkt);
                    }
                    pt = _add(buf, pt, ptlim);
                }
                continue;
            case 'T':
                pt = _fmt("%H:%M:%S", t, pt, ptlim);
                continue;
            case 't':
                pt = _add("\t", pt, ptlim);
                continue;
            case 'U':
                pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7,
                           "%02d", pt, ptlim);
                continue;
            case 'u':
                /*
                 ** From Arnold Robbins' strftime version 3.0:
                 ** "ISO 8601: Weekday as a decimal number
                 ** [1 (Monday) - 7]"
                 ** (ado, 5/24/93)
                 */
                pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday,
                           "%d", pt, ptlim);
                continue;
            case 'V':	/* ISO 8601 week number */
            case 'G':	/* ISO 8601 year (four digits) */
            case 'g':	/* ISO 8601 year (two digits) */
                /*
                 ** From Arnold Robbins' strftime version 3.0:  "the week number of the
                 ** year (the first Monday as the first day of week 1) as a decimal number
                 ** (01-53)."
                 ** (ado, 1993-05-24)
                 **
                 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
                 ** "Week 01 of a year is per definition the first week which has the
                 ** Thursday in this year, which is equivalent to the week which contains
                 ** the fourth day of January. In other words, the first week of a new year
                 ** is the week which has the majority of its days in the new year. Week 01
                 ** might also contain days from the previous year and the week before week
                 ** 01 of a year is the last week (52 or 53) of the previous year even if
                 ** it contains days from the new year. A week starts with Monday (day 1)
                 ** and ends with Sunday (day 7).  For example, the first week of the year
                 ** 1997 lasts from 1996-12-30 to 1997-01-05..."
                 ** (ado, 1996-01-02)
                 */
                {
                    int	year;
                    int	yday;
                    int	wday;
                    int	w;

                    year = t->tm_year + TM_YEAR_BASE;
                    yday = t->tm_yday;
                    wday = t->tm_wday;
                    for ( ; ; ) {
                        int	len;
                        int	bot;
                        int	top;

                        len = isleap(year) ?
                            DAYSPERLYEAR :
                            DAYSPERNYEAR;
                        /*
                         ** What yday (-3 ... 3) does
                         ** the ISO year begin on?
                         */
                        bot = ((yday + 11 - wday) %
                               DAYSPERWEEK) - 3;
                        /*
                         ** What yday does the NEXT
                         ** ISO year begin on?
                         */
                        top = bot -
                            (len % DAYSPERWEEK);
                        if (top < -3)
                            top += DAYSPERWEEK;
                        top += len;
                        if (yday >= top) {
                            ++year;
                            w = 1;
                            break;
                        }
                        if (yday >= bot) {
                            w = 1 + ((yday - bot) /
                                     DAYSPERWEEK);
                            break;
                        }
                        --year;
                        yday += isleap(year) ?
                            DAYSPERLYEAR :
                            DAYSPERNYEAR;
                    }
#ifdef XPG4_1994_04_09
                    if ((w == 52
                         && t->tm_mon == TM_JANUARY)
                        || (w == 1
                            && t->tm_mon == TM_DECEMBER))
                        w = 53;
#endif /* defined XPG4_1994_04_09 */
                    if (*format == 'V')
                        pt = _conv(w, "%02d",
                                   pt, ptlim);
                    else if (*format == 'g') {
                        pt = _conv(year % 100, "%02d",
                                   pt, ptlim);
                    } else	pt = _conv(year, "%04d",
                                           pt, ptlim);
                }
                continue;
            case 'v':
                /*
                 ** From Arnold Robbins' strftime version 3.0:
                 ** "date as dd-bbb-YYYY"
                 ** (ado, 5/24/93)
                 */
                pt = _fmt("%e-%b-%Y", t, pt, ptlim);
                continue;
            case 'W':
                pt = _conv((t->tm_yday + 7 -
                            (t->tm_wday ?
                             (t->tm_wday - 1) : 6)) / 7,
                           "%02d", pt, ptlim);
                continue;
            case 'w':
                pt = _conv(t->tm_wday, "%d", pt, ptlim);
                continue;
                /*
            case 'X':
                pt = _fmt(Locale->X_fmt, t, pt, ptlim);
                continue;
            case 'x':
                pt = _fmt(Locale->x_fmt, t, pt, ptlim);
                continue;
                */
            case 'y':
                pt = _conv((t->tm_year + TM_YEAR_BASE) % 100,
                           "%02d", pt, ptlim);
                continue;
            case 'Y':
                pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d",
                           pt, ptlim);
                continue;
            case 'Z':
                if (t->tm_zone != NULL)
                    pt = _add(t->tm_zone, pt, ptlim);
                else
                    if (t->tm_isdst == 0 || t->tm_isdst == 1) {
                        pt = _add(tzname[t->tm_isdst],
                                  pt, ptlim);
                    } else  pt = _add("?", pt, ptlim);
                continue;
            case '+':
                //pt = _fmt(Locale->date_fmt, t, pt, ptlim);
                //continue;
            case '%':
                /*
                 * X311J/88-090 (4.12.3.5): if conversion char is
                 * undefined, behavior is undefined.  Print out the
                 * character itself as printf(3) also does.
                 */
            default:
                break;
            }
        }
        if (pt == ptlim)
            break;
        *pt++ = *format;
    }
    return pt;
}

static char *
_conv(const int n, const char *const format, char *const pt, const char *const ptlim)
{
    //char	buf[INT_STRLEN_MAXIMUM(int) + 1];
    char	buf[32];

    (void) snprintf(buf, sizeof(buf), format, n);
    return _add(buf, pt, ptlim);
}

static char *
_add(const char *str, char *pt, const char *const ptlim)
{
    while (pt < ptlim && (*pt = *str++) != '\0')
        ++pt;
    return pt;
}

